#!/usr/bin/env python
from __future__ import print_function
#import hashlib
import subprocess
import numpy as np
#from XmlTools import JsonTools

class JsonObject(object):

    def __init__(self, infile):
        self.inname = infile
        fh = open(infile)
        self.data = eval(fh.read())
        fh.close()

    def save(self, outname=''):
        import json
        savename = outname or (self.inname + '_out.json')

        savedata = json.dumps(self.data, sort_keys=True, indent=4)
        fout = open(savename, 'w')
        fout.write(savedata)
        fout.close()

    def __getitem__(self, key): return self.data[key]
    def __setitem__(self, key, value): self.data[key] = value
    def __delitem__(self, key): del self.data[key]
    def __len__(self): return len(self.data)
    def __repr__(self): return repr(self.data)
    def items(self): return self.data.items()
    def keys(self): return self.data.keys()
    def values(self): return self.data.values()
    def pop(self, *args, **kwargs): return self.data.pop(*args, **kwargs)


def find_files(filename, folder='.', depth=3, args=None):
    cli = ['find', folder, '-maxdepth', str(depth), '-name', filename, '-type', 'f']
  # for key, val in kwdargs.items():
  #     cli.append(key)
  #     if val: cli.append(val)
    if isinstance(args, list): cli.extend(args)
    output = subprocess.check_output(cli).decode('ascii').strip()
    if output:
        return sorted(output.split('\n'))
    else:
        return None

outdata_dict = {}

def analyse_file(filename, item, showErr=False, fuzzy=False, ischeck=False):
    obj = JsonObject(filename)

  # Code to load the parameters of the center source
    if fuzzy:
        try:
            for _name in obj.keys():
                if item in _name:
                    name = _name
                    raise RuntimeError()
        except RuntimeError:
            pass
        else:
            return
    else:
        if item in obj.keys():
            name = item
        else:
            return

    info = obj[name]
    if isinstance(info, list) and len(info)==2 and isinstance(info[1], dict):
        info = info[1]

    outkeys = []
    outvals = []
    keys = sorted(info.keys())

    for k in keys:
        if k == 'distance':
            continue

        if k.startswith('+'):
            _k = k[1:]
        else:
            _k = k
        val = str(info[k])
        try:
            if '+/-' in val:
                values = val.split('+/-')
                if showErr:
                    outkeys.append('%s[Err]'%_k)
                    value = float(values[1])
                    if abs(value) < 1.e-50:
                        outvals.append('0.0')
                    elif abs(value) < 1.e-3 or abs(value) > 1.e5:
                        outvals.append('% .5e' % value)
                    else:
                        outvals.append('% .5f' % value)
                else:
                    outkeys.append('%s[Val]'%_k)
                    value = float(values[0])
                    if abs(value) < 1.e-50:
                        outvals.append('0.0')
                    elif abs(value) < 1.e-3 or abs(value) > 1.e5:
                        outvals.append('% .5e' % value)
                    else:
                        outvals.append('% .5f' % value)
            else:
                outkeys.append(_k)
                value = float(val)
                if k == 'NPred' or k == '+NPred' or \
                   k == 'TS value' or k == '+TSValue':
                    outvals.append('% .5f' % value)
                else:
                    if abs(value) < 1.e-50:
                        outvals.append('0.0')
                    elif abs(value) < 1.e-3 or abs(value) > 1.e5:
                        outvals.append('% .5e' % value)
                    else:
                        outvals.append('% .5f' % value)

        except ValueError as e:
            print('ERROR occurs in %s: %s'%(filename, ' '.join(str(e).strip().split('\n'))))
            while len(outkeys) > len(outvals):
                del outkeys[-1]

            while len(outkeys) < len(outvals):
                del outvals[-1]

            continue

    _obj_keys = obj.keys()
    if 'zFitQuality' in _obj_keys:
        outkeys.append('FitQuality')
        outvals.append('%i' % (obj['zFitQuality']))
    elif 'zReturnCode' in _obj_keys:
        outkeys.append('ReturnCode')
        outvals.append('%i' % (obj['zReturnCode']))

    if 'logLikelihood' in obj.keys():
        outkeys.append('filename (logLikelihood)')
        outvals.append('%s (%.3f)' % (filename, obj['logLikelihood']))
    else:
        outkeys.append('filename')
        outvals.append(filename)


  # Code to check the parameters
    checklines = []
    if ischeck:
        for name, info in obj.items():
            if isinstance(info, list) and len(info) == 2 and isinstance(info[1], dict):
                info = info[1]
            elif name == 'zCounts' or name == '+Counts':
                npred_all, npred_free, nobs = info
                free_frac = npred_free/npred_all
                if free_frac < 0.9:
                    checklines.append('  <OVERALL> npred_free/npred_all: %s' % free_frac)
                if nobs < 1:
                    checklines.append('  <OVERALL> nobs: %s' % nobs)
                else:
                    if nobs < 100.:
                        checklines.append('  <OVERALL> nobs: %s' % nobs)
                    pred_obs_frac = npred_all/nobs
                    if (pred_obs_frac > 1.01) or (pred_obs_frac < 0.99):
                        checklines.append('  <OVERALL> npred_all/nobs: %s' % pred_obs_frac)
                continue
            elif not isinstance(info, dict):
                continue

            if 'iso' in name:
                _src_keys = info.keys()
                if 'Normalization' in _src_keys:
                    i = 'Normalization'
                elif 'Prefactor' in _src_keys:
                    i = 'Prefactor'
                elif 'Integral' in _src_keys:
                    i = 'Integral'
                else:
                    i = None

                if not i is None:
                    values = str(info[i])
                    if '+/-' in values:
                        values = [float(v) for v in values.split('+/-')]
                        if values[1] > abs(values[0]) or abs(values[0] - 1.) > 0.2:
                            checklines.append('  [%s] %s: %s' % (name, i, info[i]))
                    else:
                        values = float(values)
                        if abs(values) - 1. > 0.2:
                            checklines.append('  [%s] %s: %s' % (name, i, info[i]))

            elif 'iem' in name:
                _src_keys = info.keys()
                if 'Prefactor' in _src_keys:
                    i = 'Prefactor'
                elif 'norm' in _src_keys:
                    i = 'norm'
                elif 'Value' in _src_keys:
                    i = 'Value'
                elif 'Integral' in _src_keys:
                    i = 'Integral'
                else:
                    i = None

                if not i is None:
                    values = str(info[i])
                    if '+/-' in values:
                        values = [float(v) for v in values.split('+/-')]
                        if values[1] > abs(values[0]) or abs(values[0] - 1.) > 0.05:
                            checklines.append('  [%s] %s: %s' % (name, i, info[i]))
                    else:
                        values = float(values)
                        if abs(values - 1.) > 0.05:
                            checklines.append('  [%s] %s: %s' % (name, i, info[i]))

            else:
                for i, j in info.items():
                    if (i == 'TS value' or i == '+TSValue') and float(j) < 25:
                        checklines.append('  [%s] TS: %s' % (name, j))
                    elif (i == 'NPred' or i == '+NPred') and float(j) < 1.:
                        checklines.append('  [%s] NPred: %s' % (name, j))
                    else:
                        try:
                            val_str = str(j)
                        except:
                            continue

                        if '+/-' in val_str:
                            val_float = [float(v) for v in val_str.split('+/-')]

                            if i == 'Flux' or i == '+Flux':
                                if 2.*val_float[1] > abs(val_float[0]):
                                    checklines.append('  [%s] Flux: %s' % (name, j))

                            elif abs(val_float[1]) >= 1.e-8*abs(val_float[0]):
                                haserror = False
                                if 2.*val_float[1] > abs(val_float[0]):
                                    haserror = True
                                  # errorInfo = 'Error too big: val < 2.*err'
                                elif (val_float[0] != 0) and \
                                     (abs(val_float[0]/10.**np.floor(np.log10(abs(val_float[0]))) - \
                                      round(val_float[0]/10.**np.floor(np.log10(abs(val_float[0]))))) < 0.01):
                                    haserror = True
                                  # errorInfo = 'Parameter at limit?'
                                elif (i == 'Index' or i == 'Index1' or i == 'alpha') and \
                                     (val_float[0] > 3 or -1 < val_float[0] < 1 or val_float[0] < -3):
                                    haserror = True
                                  # errorInfo = 'abnormal index: -index<1 or -index>3'
                                elif i == 'beta' and (val_float[0] < 0):
                                    haserror = True
                                  # errorInfo = 'abnormal index: -beta<0'

                                if haserror:
                                    checklines.append('  [%s] %s: %s' % (name, i, j))
                                  # checklines.append('  [%s] %s: %s (%s)' % (name, i, j, errorInfo))

  # make print strings
    keys_to_print = outkeys[0]
    vals_to_print = outvals[0]
    for k, v in zip(outkeys[1:], outvals[1:]):
        lk, lv = len(keys_to_print), len(vals_to_print)
        if lv >= lk:
            keys_to_print += ' '*(lv-lk+5) + k
            vals_to_print += ' '*5 + v
        else:
            keys_to_print += ' '*5 + k
            vals_to_print += ' '*(lk-lv+5) + v

    checklines_to_print = '\n'.join(checklines)

    global outdata_dict
    #md5num = hashlib.md5('\t'.join(outkeys))#.hexdigest()
    md5num = '\t'.join(outkeys)
    if md5num in outdata_dict.keys():
        outdata_dict[md5num].append((keys_to_print, vals_to_print, checklines_to_print))
    else:
        outdata_dict[md5num] = [(keys_to_print, vals_to_print, checklines_to_print)]


def cli():
    import argparse
    helpString = 'Read results.dat folders recursively, and print information on the particular source.'

    parser = argparse.ArgumentParser(description=helpString, prefix_chars='-+')
    parser.add_argument("source", type=str, help="The source you care about.")
    parser.add_argument("-file", type=str, default="results*dat", help="The name of file you want to interpret.")
    parser.add_argument("-folder", type=str, default=".", help="The folder to find.")
    parser.add_argument("-maxdepth", type=int, default="3", help="The maximum folder depth")
    parser.add_argument("+fuzzy", action='store_true', default=False, help="Fuzzy search the source name")
    parser.add_argument("+check", action='store_true', default=False, help="wether to check the results")
    parser.add_argument("+error", action='store_true', default=False, help="Show errors")
    parser.add_argument('args', nargs=argparse.REMAINDER, help='remainding argmuments transmitted to "find"')

    args = parser.parse_args()

  # print(args)
    files = find_files(args.file, args.folder, args.maxdepth, args.args)
    if files is None:
        print('No file which satisfies "%s" is found!' % args.file)
        return

  # analyse files
    for f in files:
        analyse_file(f, args.source, args.error, args.fuzzy, args.check)

    global outdata_dict
    if len(outdata_dict) > 0:
        for ol in outdata_dict.values():
            isprintkey = False
            if len(ol) == 0:
                raise RuntimeError('len(ol) > 0')

            for k, v, c in ol:
                if isprintkey:
                    print(v)
                    if len(c) > 0:
                        print(c)
                        isprintkey = False
                    else:
                        isprintkey = True
                else:
                    print('-'*len(k))
                    print(k)
                    print(v)
                    if len(c) > 0:
                        print(c)
                        isprintkey = False
                    else:
                        isprintkey = True
    else:
        print('No source named as "%s" is found' % args.source)

if __name__ == '__main__': cli()
